{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Teil 8 - Einleitung für Pläne\n",
    "\n",
    "\n",
    "### Kontext \n",
    "\n",
    "Hier wird ein entscheidendes Objekt vorgestellt, um Federated Learning in industriellen Größenordnungen umzusetzen: der Plan  \n",
    "Er reduziert die benötigte Bandbreite dramatisch, ermöglich asynchrone Ansätze und gewährt den ferngesteuerten Helfern mehr Autonomie. Das original Konzept für Pläne kann in dem Paper [\"Towards Federated Learning at Scale: System Design\"](https://arxiv.org/pdf/1902.01046.pdf) nachgelsesen werden. In diesem Fall wurde es auf die Anforderungen der PySyft Bibiliothek angepasst.\n",
    "\n",
    "Ein Plan ist zum Speichern einer Aneinanderreihung von Torch Operationen gedacht. Damit gleicht er einer Funktion, kann jedoch diese Aneinanderreihung zu ferngesteuerten Helfern senden und dabei eine Referenz darauf selbst behalten. Somit kann eine Sequenz von $n$ Operationen, mit den zugehörigen Pointern, mittels einer einzigen Nachricht übermittelt werden, anstatt für jede der Operationen eine eigene Nachricht senden zu müssen. Es ist sogar möglich festgelegte Tensoren (sogenannte _State Tensoren_) mitzusenden und damit erweiterte Funktionalitäten zu nutzen. Pläne können entweder als zu sendende Funktion oder als send- und ausführbare Klasse aufgefasst werden.  \n",
    "Infolgedessen können Nutzer die Pläne auf höchstem Level als magische Fähigkeit auffassen, welche es erlaubt Sequenzen von Torch Funktionen auf Geräten hintereinander ausführen zu lassen. \n",
    "\n",
    "Eine wichtige Anmerkung ist, dass die Klasse aller verwendbaren Funktionen in den Plänen momentan noch ausschließlich auf Aneinanderreihungen von Torch Operationen limitiert sind. Dies schließt speziell die logischen Strukturen wie `if`, `for` und `while` aus, auch wenn aktuell an Notlösungen gearbeitet wird. _Um es ganz genau zu nehmen, können die logischen Statements doch eingebaut werden, allerdings legt die erste Auswertung des Statements fest, wie es im Folgenden jedesmal ausgeführt wird. In den meisten Fällen ist solches Verhalten nicht wünschenswert._\n",
    "\n",
    "Autoren:\n",
    "- Théo Ryffel - Twitter [@theoryffel](https://twitter.com/theoryffel) - GitHub: [@LaRiffle](https://github.com/LaRiffle)\n",
    "- Bobby Wagner - Twitter [@bobbyawagner](https://twitter.com/bobbyawagner) - GitHub: [@robert-wagner](https://github.com/robert-wagner)\n",
    "- Marianne Monteiro - Twitter [@hereismari](https://twitter.com/hereismari) - GitHub: [@mari-linhares](https://github.com/mari-linhares)\n",
    "\n",
    "Übersetzer:\n",
    "- Jan Moritz Behnken - Github: [@JMBehnken](https://github.com/JMBehnken)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Importe und Model Spezifikationen\n",
    "\n",
    "Zuerst werden die offiziellen Importe getätigt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T10:44:51.982147Z",
     "start_time": "2020-04-03T10:44:51.692349Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Danach folgen die Importe spezifisch zu PySyft. Eine wichtige Anmerkung ist hier, dass **ein lokaler Helfer kein Klient-Helfer sein sollte**. *Nur nicht-Klienten-Helfer können Objekte speichern, was für die Fähigkeit zum Ausführen eines Planes bedeutsam ist.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T10:48:26.576904Z",
     "start_time": "2020-04-03T10:48:24.802468Z"
    }
   },
   "outputs": [],
   "source": [
    "import syft as sy  # import the Pysyft library\n",
    "hook = sy.TorchHook(torch)  # hook PyTorch ie add extra functionalities \n",
    "\n",
    "# IMPORTANT: Local worker should not be a client worker\n",
    "hook.local_worker.is_client_worker = False\n",
    "\n",
    "\n",
    "server = hook.local_worker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Die ferngesteuerten Helfer oder _Geräte_ werden nach dem Schema aus dem referenzierten Artikel benannt.  \n",
    "Anschließend werden die Helfer mit Daten ausgestattet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T10:49:56.131380Z",
     "start_time": "2020-04-03T10:49:56.111410Z"
    }
   },
   "outputs": [],
   "source": [
    "x11 = torch.tensor([-1, 2.]).tag('input_data')\n",
    "x12 = torch.tensor([1, -2.]).tag('input_data2')\n",
    "x21 = torch.tensor([-1, 2.]).tag('input_data')\n",
    "x22 = torch.tensor([1, -2.]).tag('input_data2')\n",
    "\n",
    "device_1 = sy.VirtualWorker(hook, id=\"device_1\", data=(x11, x12)) \n",
    "device_2 = sy.VirtualWorker(hook, id=\"device_2\", data=(x21, x22))\n",
    "devices = device_1, device_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Basis Beispiel\n",
    "\n",
    "Eine Funktion wird definiert, welche anschließend in einen Plan umgewandelt werden soll. Um dies zu erreichen, reicht es aus einen passenden Dekorator über die Funktion zu schreiben!\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:28:15.971549Z",
     "start_time": "2020-04-03T11:28:15.963078Z"
    }
   },
   "outputs": [],
   "source": [
    "@sy.func2plan()\n",
    "def plan_double_abs(x):\n",
    "    x = x + x\n",
    "    x = torch.abs(x)\n",
    "    return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eine Überprüfung bestätigt den Erfolg."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:28:34.108257Z",
     "start_time": "2020-04-03T11:28:34.090667Z"
    }
   },
   "outputs": [],
   "source": [
    "plan_double_abs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Um einen Plan zu verwenden, müssen zwei Dinge abgeschlossen sein:\n",
    "- das Bauen des Plans (_das Registrieren der Sequenz an Operationen in der Funktion_)\n",
    "- das Senden des Plans zum Helfer / Gerät\n",
    "\n",
    "#### Bauen des Plans\n",
    "\n",
    "Um den Plan zu bauen, muss er nur mit einigen Daten aufgerufen werden. \n",
    "\n",
    "Gestartet wird mit dem Aufruf einiger Daten:  \n",
    "Eine Anfrage wird dabei über das Netzwerk gesendet und ein Referenz Pointer auf die Daten zurückgegeben."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:34:53.305839Z",
     "start_time": "2020-04-03T11:34:53.263193Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_to_data = device_1.search('input_data')[0]\n",
    "pointer_to_data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Beim Versuch den Plan auf den Daten des Gerätes `location:device_1` auszuführen, wird ein Fehler verursacht werden, da der Plan noch nicht gebaut wurde. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:36:53.834945Z",
     "start_time": "2020-04-03T11:36:53.826213Z"
    }
   },
   "outputs": [],
   "source": [
    "plan_double_abs.is_built"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:37:00.804171Z",
     "start_time": "2020-04-03T11:37:00.795620Z"
    }
   },
   "outputs": [],
   "source": [
    "# Sending non-built Plan will fail\n",
    "try:\n",
    "    plan_double_abs.send(device_1)\n",
    "except RuntimeError as error:\n",
    "    print(error)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Um den Plan zu bauen, muss er nur mit den benötigten Argumenten (a. k. a. einigen Daten) und der `build` Methode aufgerufen werden. Nachdem ein Plan gebaut wurde, wird er alle aneinander gereihten Befehle ausführen und in seinem Attribut `actions` abspeichern!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:40:36.532203Z",
     "start_time": "2020-04-03T11:40:36.523804Z"
    }
   },
   "outputs": [],
   "source": [
    "plan_double_abs.build(torch.tensor([1., -2.]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:40:37.776718Z",
     "start_time": "2020-04-03T11:40:37.767086Z"
    }
   },
   "outputs": [],
   "source": [
    "plan_double_abs.is_built"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Wird der Plan nun gesendet, so funktionert es!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:40:56.782839Z",
     "start_time": "2020-04-03T11:40:56.770931Z"
    }
   },
   "outputs": [],
   "source": [
    "# This cell is executed successfully\n",
    "pointer_plan = plan_double_abs.send(device_1)\n",
    "pointer_plan"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Genau wie bei Tensoren, wird ein Pointer auf das gesendete Objekt zurückgegeben. In diesem Falle ist es ein `PointerPlan`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es ist wichtig sich in Erinnerung zu rufen, dass beim Bauen des Plans alle Ids der Speicherorte für die Ergebnisse festgelegt werden, bevor die eigentlichen Berechnungen starten. Dies ermöglicht ein asynchrones senden der Befehle, da ein Referenz Pointer zurückgegeben wird, bevor die Berechnungen auf dem Helfer abgeschlossen sind. Somit lässt sich mit lokalen Befehlen fortfahren, ohne auf den Helfer warten zu müssen. Eine wichtige Anwendung dafür ist z. B. das Starten einer Berechnung eines Daten-Batches auf Gerät_1 und ohne auf das Ergebnis abwarten zu müssen, kann auf Gerät_2 ein weiterer Daten-Batch bearbeitet werden."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Starten eines Planes aus der Ferne\n",
    "\n",
    "Der Plan kann nun aus der Ferne gestartet werden, indem der Pointer zum Plan mit einem Pointer zu den Daten aufgerufen wird. Dies veranlasst die Ausführung des Plans und die Ergebnisse werden an den vorher festgelegten Orten abgelegt. Ein Pluspunkt ist, dass alles nur einer einzigen Kommunikationsrunde bedurfte. \n",
    "\n",
    "Das Ergebnis ist ein einfacher Pointer, wie er schon von den normalen Torch Funktionen bekannt ist!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:58:40.819616Z",
     "start_time": "2020-04-03T11:58:40.807365Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_to_result = pointer_plan(pointer_to_data)\n",
    "print(pointer_to_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dieses Ergebnis kann einfach zurückgeholt werden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T11:59:05.789928Z",
     "start_time": "2020-04-03T11:59:05.776974Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Einem konkreten Beispiel entgegen\n",
    "\n",
    "Eigentlich soll solch ein Plan jedoch für Deep und Federated Learning genutzt werden, nicht wahr? Lassen Sie uns ein komplizierteres Beispiel mit einem Neuronalen Netzwerk betrachten.  \n",
    "Anzumerken ist, dass nun eine Klasse in einen Plan übertragen wird. Dies kann erreicht werden, indem die neue Klasse von der `sy.Plan` Klasse erbt (anstelle des üblichen `nn.Module`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:05:33.411705Z",
     "start_time": "2020-04-03T12:05:33.401980Z"
    }
   },
   "outputs": [],
   "source": [
    "class Net(sy.Plan):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(2, 3)\n",
    "        self.fc2 = nn.Linear(3, 2)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:05:34.002405Z",
     "start_time": "2020-04-03T12:05:33.996169Z"
    }
   },
   "outputs": [],
   "source": [
    "net = Net()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:05:34.419019Z",
     "start_time": "2020-04-03T12:05:34.411927Z"
    }
   },
   "outputs": [],
   "source": [
    "net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Der Plan wird mit einigen Pseudo-Daten gebaut."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:06:19.622833Z",
     "start_time": "2020-04-03T12:06:19.602600Z"
    }
   },
   "outputs": [],
   "source": [
    "net.build(torch.tensor([1., 2.]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nun wird der Plan an den ferngesteuerten Helfer gesendet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:06:41.488514Z",
     "start_time": "2020-04-03T12:06:41.475850Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_to_net = net.send(device_1)\n",
    "pointer_to_net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Anschließend werden noch einige Daten benötigt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:07:09.638458Z",
     "start_time": "2020-04-03T12:07:09.631612Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_to_data = device_1.search('input_data')[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Die Syntax ist nun identisch zum normalen Ausführen von Befehlen auf der lokalen Maschine. Verglichen mit der klassischen Fernsteuerung wird jedoch nur eine einzige Kommunikationsrunde für die Ausführung benötigt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:09:11.890376Z",
     "start_time": "2020-04-03T12:09:11.880001Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_to_result = pointer_to_net(pointer_to_data)\n",
    "pointer_to_result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Das Ergebnis lässt sich wie gewöhnlich erhalten!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:09:34.992998Z",
     "start_time": "2020-04-03T12:09:34.979680Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et voilà! Die Kommunikation zwischen lokaler Maschine (oder dem Server) und dem ferngesteuerten Gerät konnte dramatisch reduziert werden."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Wechseln zwischen Helfern\n",
    "\n",
    "Eine weitere wichtige und wünschenswerte Fähigkeit ist das Wiederverwenden des Planes auf mehreren Helfern mit unterschiedlichen Daten-Batches.  \n",
    "Ein Neubau des Planes beim Wechsel des Helfers soll hierbei vermieden werden. Im Folgenden wird das obrige Beispiel daraufhin angepasst."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:14:36.594357Z",
     "start_time": "2020-04-03T12:14:36.582704Z"
    }
   },
   "outputs": [],
   "source": [
    "class Net(sy.Plan):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(2, 3)\n",
    "        self.fc2 = nn.Linear(3, 2)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:14:37.733208Z",
     "start_time": "2020-04-03T12:14:37.724353Z"
    }
   },
   "outputs": [],
   "source": [
    "net = Net()\n",
    "\n",
    "# Build plan\n",
    "net.build(torch.tensor([1., 2.]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dies sind die wichtigsten Schritte die auszuführen waren."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:15:13.439984Z",
     "start_time": "2020-04-03T12:15:13.425709Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_to_net_1 = net.send(device_1)\n",
    "pointer_to_data = device_1.search('input_data')[0]\n",
    "pointer_to_result = pointer_to_net_1(pointer_to_data)\n",
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tatsächlich lassen sich andere PointerPlans einfach vom selben Plan aus nutzen und so bleibt die Syntax fürs Verwenden auf anderen Geräten identisch."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:16:58.885074Z",
     "start_time": "2020-04-03T12:16:58.870246Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_to_net_2 = net.send(device_2)\n",
    "pointer_to_data = device_2.search('input_data')[0]\n",
    "pointer_to_result = pointer_to_net_2(pointer_to_data)\n",
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Anmerkung: Aktuell lässt sich mit der Plan Klasse nur eine einzige Methode namens \"forward\" verwenden."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Automatisch Pläne bauen, welche auch Funktionen sind\n",
    "\n",
    "Für Funktionen (`@` `sy.func2plan`) kann der Plan automatisch gebaut werden ohne explizit die Methode `build` aufrufen zu müssen. In solchen Fällen ist der Plan direkt beim Erstellen gebaut. \n",
    "\n",
    "Um diese Funktionalität direkt nutzen zu können, muss der Dekorator nur mit dem zusätzlichen Argument `args_shape` aufgerufen werden. Dieses muss eine Liste aller Shapes der Funktions-Argumente enthalten."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:24:03.637943Z",
     "start_time": "2020-04-03T12:24:03.629255Z"
    }
   },
   "outputs": [],
   "source": [
    "@sy.func2plan(args_shape=[(-1, 1)])\n",
    "def plan_double_abs(x):\n",
    "    x = x + x\n",
    "    x = torch.abs(x)\n",
    "    return x\n",
    "\n",
    "plan_double_abs.is_built"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Der `args_shape` Parameter wird intern genutzt um Pseudo-Tensoren zu erschaffen, welche wiederum zum Bau des Plans verwendet werden."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:25:34.951555Z",
     "start_time": "2020-04-03T12:25:34.942076Z"
    }
   },
   "outputs": [],
   "source": [
    "@sy.func2plan(args_shape=[(1, 2), (-1, 2)])\n",
    "def plan_sum_abs(x, y):\n",
    "    s = x + y\n",
    "    return torch.abs(s)\n",
    "\n",
    "plan_sum_abs.is_built"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Auch ist es möglich Zustands-Elemente der Funktion zu übergeben!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:26:37.448805Z",
     "start_time": "2020-04-03T12:26:37.440083Z"
    }
   },
   "outputs": [],
   "source": [
    "@sy.func2plan(args_shape=[(1,)], state=(torch.tensor([1]), ))\n",
    "def plan_abs(x, state):\n",
    "    bias, = state.read()\n",
    "    x = x.abs()\n",
    "    return x + bias"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T12:26:39.643269Z",
     "start_time": "2020-04-03T12:26:39.629702Z"
    }
   },
   "outputs": [],
   "source": [
    "pointer_plan = plan_abs.send(device_1)\n",
    "x_ptr = torch.tensor([-1, 0]).send(device_1)\n",
    "p = pointer_plan(x_ptr)\n",
    "p.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Um das Wissen zu vertiefen, kann das Tutorial \"Part 8 bis\" mit der Verwendung von Plänen und Protokollen verwendet werden!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### PySyft auf GitHub einen Stern geben! \n",
    "\n",
    "Der einfachste Weg, unserer Community zu helfen, besteht darin, die GitHub-Repos mit Sternen auszuzeichnen! Dies hilft, das Bewusstsein für die coolen Tools zu schärfen, die wir bauen. \n",
    "\n",
    "- [Gib PySyft einen Stern](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Nutze unsere Tutorials auf GitHub!\n",
    "\n",
    "Wir haben hilfreiche Tutorials erstellt, um ein Verständnis für Federated und Privacy-Preserving Learning zu entwickeln und zu zeigen wie wir die einzelnen Bausteine weiter entwickeln.\n",
    "\n",
    "- [PySyft Tutorials ansehen](https://github.com/OpenMined/PySyft/tree/master/examples/tutorials)\n",
    "\n",
    "\n",
    "### Mach mit bei Slack! \n",
    "\n",
    "Der beste Weg, um über die neuesten Entwicklungen auf dem Laufenden zu bleiben, ist, sich unserer Community anzuschließen! Sie können dies tun, indem Sie das Formular unter [http://slack.openmined.org](http://slack.openmined.org) ausfüllen.\n",
    "\n",
    "### Treten Sie einem Code-Projekt bei! \n",
    "\n",
    "Der beste Weg, um zu unserer Community beizutragen, besteht darin, Entwickler zu werden! Sie können jederzeit zur PySyft GitHub Issues-Seite gehen und nach \"Projects\" filtern. Dies zeigt Ihnen alle Top-Level-Tickets und gibt einen Überblick darüber, an welchen Projekten Sie teilnehmen können! Wenn Sie nicht an einem Projekt teilnehmen möchten, aber ein wenig programmieren möchten, können Sie auch nach weiteren \"einmaligen\" Miniprojekten suchen, indem Sie nach GitHub-Problemen suchen, die als \"good first issue\" gekennzeichnet sind. \n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Spenden\n",
    "\n",
    "Wenn Sie keine Zeit haben, zu unserer Codebase beizutragen, aber dennoch Unterstützung leisten möchten, können Sie auch Unterstützer unseres Open Collective werden. Alle Spenden fließen in unser Webhosting und andere Community-Ausgaben wie Hackathons und Meetups! \n",
    "\n",
    " - [OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}